#include <yam/type.h>

#include <vector>
#include <regex>

namespace yam {

bool isHex(char c) {
    return ('0' <= c and c <= '9') or
           ('A' <= c and c <= 'F') or
           ('a' <= c and c <= 'f');
}

bool isOct(char c) {
    return '0' <= c and c <= '7';
}

bool isDec(char c) {
    return '0' <= c and c <= '9';
}

struct IntType : Type {
    IntType() : Type("tag:yaml.org,2002:int", "scalar") {
        resolve = [](const yaml& data) {
            if (not data.is_string())
                return false;

            string str = data;
            size_t max = str.size();
            size_t index = 0;

            if (not max)
                return false;

            // sign
            if (str[index] == '-' or str[index] == '+')
                ++index;

            if (index == max)
                return false;

            if (str[index] == '0') {
                ++index;

                // 0
                if (index == max)
                    return true;

                if (str[index] == 'b') {  // base 2
                    ++index;
                    bool hasDigits = false;
                    for (; index < max; ++index) {
                        if (str[index] == '_')
                            continue;
                        else if (str[index] != '0' and str[index] != '1')
                            return false;
                        hasDigits = true;
                    }
                    return hasDigits;
                }
                else if (str[index] == 'x') {  // base 16
                    ++index;
                    bool hasDigits = false;
                    for (; index < max; ++index) {
                        if (str[index] == '_')
                            continue;
                        else if (not isHex(str[index]))
                            return false;
                        hasDigits = true;
                    }
                    return hasDigits;
                }
                else {
                    bool hasDigits = false;
                    for (; index < max; ++index) {
                        if (str[index] == '_')
                            continue;
                        else if (not isOct(str[index]))
                            return false;
                        hasDigits = true;
                    }
                    return hasDigits;
                }
            }

            // base 10 (except 0) or base 60
            bool hasDigits = false;
            for (; index < max; ++index) {
                if (str[index] == '_')
                    continue;
                else if (str[index] == ':')
                    break;
                else if (not isDec(str[index]))
                    return false;
                hasDigits = true;
            }

            if (not hasDigits)
                return false;

            // if not base 60, done
            if (str[index] != ':')
                return true;

            // base60 almost not used, no needs to optimize
            return std::regex_match(str.cbegin() + index, str.cend(), std::regex("^(:[0-5]?[0-9])+$"));
        };

        construct = [](const yaml& data) -> yaml {
            string str = data;

            if (str.find('_') != string::npos)
                str.erase(std::remove_if(str.begin(), str.end(), [](char c) { return c == '_'; }), str.end());

            int sign = 1;
            if (str[0] == '-' or str[0] == '+') {
                if (str[0] == '-')
                    sign = -1;
                str = string(str.cbegin() + 1, str.cend());
            }

            if (str == "0")
                return 0;

            if (str[0] == '0') {
                if (str[1] == 'b')
                    return sign * std::stoi(string(str.cbegin() + 2, str.cend()), 0, 2);
                else if (str[1] == 'x')
                    return sign * std::stoi(string(str.cbegin() + 2, str.cend()), 0, 16);
                else
                    return sign * std::stoi(string(str.cbegin() + 1, str.cend()), 0, 8);
            }

            if (str.find(':') != string::npos) {
                vector<int> digits;
                for (size_t start = 0, end = 0; end <= str.size(); ++end) {
                    if (end == str.size() or str[end] == ':') {
                        digits.push_back(std::stoi(string(str.cbegin() + start, str.cbegin() + end)));
                        start = end + 1;
                    }
                }

                int value = 0;
                int base = 1;
                for (auto it = digits.crbegin(); it != digits.crend(); ++it) {
                    value += *it * base;
                    base *= 60;
                }

                return sign * value;
            }

            return sign * std::stoi(str);
        };
    }
};

}
